package com.roadmap.client.module.plan;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.log4j.Logger;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;

import com.roadmap.client.module.BaseManager;
import com.roadmap.common.BaseConst;
import com.roadmap.common.BaseEntity;
import com.roadmap.common.RdcLogger;
import com.roadmap.common.RdcResourceBundle;
import com.roadmap.common.BaseEnum.ActionType;
import com.roadmap.entities.Aspirant;
import com.roadmap.entities.LifeGoal;
import com.roadmap.entities.LifePlan;
import com.roadmap.entities.ext.PlanExt;
import com.roadmap.exception.ActionNotSupportedException;
import com.roadmap.exception.EntityNotFoundException;
import com.roadmap.exception.InvalidActionException;
import com.roadmap.exception.InvalidQueryException;
import com.roadmap.exception.InvalidValueException;
import com.roadmap.exception.NullObjectException;
import com.roadmap.exception.ServerException;
import com.roadmap.repo.db.RdcQuery;

public class PlanManager extends BaseManager {
	private final static Logger _log = RdcLogger.getLogger(PlanManager.class);

	/** In the constructor, the parent of this manager is set to NULL */
	public PlanManager(String aspirantId) {
		super(aspirantId, LifePlan.class);
	}

	public PlanManager(String aspirantId, LifeGoal parent) {
		super(aspirantId, LifePlan.class, parent);
	}

	public PlanManager(String aspirantId, Aspirant parent) {
		super(aspirantId, LifePlan.class, parent);
	}

	public void create(BaseEntity entity) throws NullObjectException,
			InvalidActionException, InvalidValueException, ServerException,
			EntityNotFoundException, ActionNotSupportedException {
		if (!checkEntityIsValid(entity))
			return;

		LifeGoal goal = (LifeGoal) getParent();
		if (goal == null) {
			String msg = "the parent of plan manager is NULL.";
			NullObjectException obe = new NullObjectException();
			_log.error(msg, obe);
			throw obe;
		}
		// only when the goal's status is UNSCHEDULED, can a new plan be created
		// for it
		if (goal.getStatus() == BaseConst.LIFEGOAL_STATUS_UNSCHEDULED) {
			goal.setStatus(BaseConst.LIFEGOAL_STATUS_SCHEDULED);
			_entities.removeAllElements();
			_entities.add(entity);
			_entities.add(goal);
			getDbUtil().saveOrUpdateEntities(_entities);
			getNoticeManager().notifyForEntry(entity,
					ActionType.CREATE, null);
		}
		else {
			ActionNotSupportedException anse = new ActionNotSupportedException();
			String msg = "The current status of the goal doesn't support this action.";
			_log.error(msg, anse);
			throw anse;
		}
	}

	@Override
	@Deprecated
	public String getEntityHealthy(BaseEntity entity)
			throws ActionNotSupportedException, InvalidValueException {
		if (!checkEntityIsValid(entity)) {
			throw new InvalidValueException();
		}

		if (!(getParent() instanceof LifeGoal)) {
			InvalidValueException ive = new InvalidValueException();
			_log.error("The parant is not a goal", ive);
			throw ive;
		}

		LifeGoal goal = (LifeGoal) getParent();
		int status = goal.getStatus();
		// only when status is undergoing or scheduled or unscheduled,
		// validation is needed
		if (status == BaseConst.LIFEGOAL_STATUS_UNSCHEDULED
				|| status == BaseConst.LIFEGOAL_STATUS_SCHEDULED
				|| status == BaseConst.LIFEGOAL_STATUS_UNDERGOING) {
			LifePlan plan = (LifePlan) entity;
			Date today = new Date();
			Date start = plan.getScheduledStartDate();
			Date end = plan.getScheduledEndDate();
			// if the start time is before today but the status is still
			// Scheduled
			if (status == BaseConst.LIFEGOAL_STATUS_SCHEDULED
					&& start.before(today)) {
				return RdcResourceBundle.Entity_Symptom_Not_Started;
			}

			if (status == BaseConst.LIFEGOAL_STATUS_UNDERGOING
					&& end.before(today)) {
				return RdcResourceBundle.Entity_Symptom_Not_Ended;
			}
		}

		return RdcResourceBundle.Entity_Symptom_Healthy;
	}

	private String diagnose(PlanExt plan) throws ActionNotSupportedException,
			InvalidValueException {
		int status = plan.getStatus();
		// only when status is undergoing or scheduled or unscheduled,
		// validation is needed
		if (status == BaseConst.LIFEGOAL_STATUS_UNSCHEDULED
				|| status == BaseConst.LIFEGOAL_STATUS_SCHEDULED
				|| status == BaseConst.LIFEGOAL_STATUS_UNDERGOING) {
			Date today = new Date();
			Date start = plan.getScheduledStartDate();
			Date end = plan.getScheduledEndDate();
			// if the start time is before today but the status is still
			// Scheduled
			if (status == BaseConst.LIFEGOAL_STATUS_SCHEDULED
					&& start.before(today)) {
				return RdcResourceBundle.Entity_Symptom_Not_Started;
			}

			if (status == BaseConst.LIFEGOAL_STATUS_UNDERGOING
					&& end.before(today)) {
				return RdcResourceBundle.Entity_Symptom_Not_Ended;
			}
		}

		return RdcResourceBundle.Entity_Symptom_Healthy;
	}

	@Override
	/** entity is a plan whose id and goal id are not null */
	public void delete(BaseEntity entity) throws NullObjectException,
			InvalidActionException, InvalidValueException, ServerException,
			EntityNotFoundException, ActionNotSupportedException {
		if (!checkEntityIsValid(entity)) {
			return;
		}

		LifePlan plan = (LifePlan) entity;
		// update the goal and task
		Session session = getDbUtil().getSession();
		Transaction tx = null;
		try {
			tx = session.beginTransaction();
			Date dt = new Date();
			// update the goal. do nothing to the plan, bcz the plan's status
			// equals to its goal's.
			String hql = "update LifeGoal set status=:state, updatedTime=:dt where id=:gid";
			Query q = session.createQuery(hql);
			q.setParameter("gid", plan.getGoalId());
			q.setParameter("state", BaseConst.LIFEGOAL_STATUS_UNSCHEDULED);
			q.setParameter("dt", dt);
			q.executeUpdate();

			// update the task
			hql = "update LifeTask set status=:state where planId=:pid";
			q = session.createQuery(hql);
			q.setParameter("pid", plan.getId());
			q.setParameter("state", BaseConst.LIFETASK_STATUS_DELETED);
			q.executeUpdate();
			
			// delete the plan
			hql = "delete from LifePlan where planId=:pid";
			q = session.createQuery(hql);
			q.setParameter("pid", plan.getId());
			q.executeUpdate();

			tx.commit();
		}
		catch (HibernateException e) {
			_log.error(e);
			if (tx != null) {
				tx.rollback();
			}
		}
		finally {
			session.close();
		}
	}

	@Override
	public List<PlanExt> getExecutableEntities()
			throws ActionNotSupportedException, InvalidQueryException,
			InvalidValueException, ServerException {
		String sql = "select {plan.*},goal.name,goal.status from lifeplan as plan, lifegoal as goal where goal.aspirantId=? and (goal.status=? || goal.status=? )and plan.goalId=goal.goalId order by plan.createdTime DESC";
		RdcQuery query = getDbUtil().createNativeSQLQuery(sql);
		query.addEntity("plan", LifePlan.class);
		query.addScalar("name");
		query.addScalar("status");

		query.setParameters(new Object[] { getOwnerId(),
				BaseConst.LIFEGOAL_STATUS_SCHEDULED,
				BaseConst.LIFEGOAL_STATUS_UNDERGOING });
		List list = query.getResults();
		if (list == null || list.isEmpty()) {
			return null;
		}

		List<PlanExt> plans = new ArrayList<PlanExt>();
		String[] planIDs = new String[list.size()];
		int count = 0;
		for (int i = 0; i < list.size(); i++) {
			Object[] result = (Object[]) list.get(i);
			PlanExt ext = new PlanExt((LifePlan) result[0]);
			ext.setGoalName((String) result[1]);
			ext.setStatus(((Byte) result[2]).intValue());
			ext.setSymptom(diagnose(ext));
			plans.add(ext);

			planIDs[count++] = ext.getId();
		}

		sql = "select t.planId as pid, count(t.taskId) as amount, sum(t.proportionOfPlan * (select sum(e.proportionOfTask) from Execution as e where e.taskId=t.taskId group by e.taskId)) as progress from LifeTask as t where t.planId in(:planIDs) and t.status<>:state group by t.planId";
		query = getDbUtil().createNativeSQLQuery(sql);
		query.addScalar("pid", Hibernate.STRING);
		query.addScalar("amount", Hibernate.INTEGER);
		query.addScalar("progress", Hibernate.INTEGER);
		query.setParameterList("planIDs", planIDs);
		query.setParameter("state", BaseConst.LIFETASK_STATUS_DELETED);
		List results = query.getResults(); // getDbUtil().executeHQLQeury(hql,
		// "planIDs", planIDs);
		if (results != null && !results.isEmpty()) {
			for (int i = 0; i < plans.size(); i++) {
				PlanExt ext = plans.get(i);
				for (int j = 0; j < results.size(); j++) {
					Object[] result = (Object[]) results.get(j);
					if (ext.getId().equals((String) result[0])) {
						ext.setTaskCount(((Integer) result[1]).intValue());
						// the plan may not have executions
						if (result[2] != null) {
							ext
									.setProgress(((Integer) result[2])
											.intValue() / 100);
						}
						// if this node was hit, it wouldn't be next time
						results.remove(j);
						break;
					}
				}
			}
		}
		return plans;
	}

	@Override
	public PlanExt getEntityExt(String entityId) throws NullObjectException,
			InvalidQueryException, ServerException, InvalidValueException {
		if (entityId == null) {
			InvalidValueException e = new InvalidValueException(
					"NULL entity Id");
			_log.error(e);
			throw e;
		}

		// query the plan's information
		String sql = "select {plan.*},goal.name,goal.status from lifeplan as plan, lifegoal as goal where goal.aspirantId=? and plan.planId=? and plan.goalId=goal.goalId and goal.status<>?";
		RdcQuery query = getDbUtil().createNativeSQLQuery(sql);
		query.addEntity("plan", LifePlan.class);
		query.addScalar("name");
		query.addScalar("status");
		query.setParameters(new Object[] { getOwnerId(), entityId,
				BaseConst.LIFEGOAL_STATUS_DELETED });
		Object[] result = (Object[]) query.getUniqueResult();
		if (result == null) {
			return null;
		}
		PlanExt plan = null;
		if (result != null) {
			plan = new PlanExt((LifePlan) result[0]);
			plan.setGoalName((String) result[1]);
			plan.setStatus(((Byte) result[2]).intValue());
		}

		// the plan's progress
		sql = "select count(t.taskId) as amount, sum(t.proportionOfPlan) as assigned, sum(t.proportionOfPlan * (select sum(e.proportionOfTask) from Execution as e where e.taskId=t.taskId group by e.taskId)) as progress from LifeTask as t where t.planId=:pid and t.status<>:state";
		query = getDbUtil().createNativeSQLQuery(sql);
		query.addScalar("amount", Hibernate.INTEGER);
		query.addScalar("assigned", Hibernate.INTEGER);
		query.addScalar("progress", Hibernate.INTEGER);
		query.setParameter("pid", entityId);
		query.setParameter("state", BaseConst.LIFETASK_STATUS_DELETED);
		result = (Object[]) query.getUniqueResult();
		if (result != null) {
			plan.setTaskCount(((Integer) result[0]).intValue());
			// the plan may not have executions
			if (result[1] != null) {
				plan.setAssignedProportion(((Integer) result[1]).intValue());
			}
			if (result[2] != null) {
				plan.setProgress(((Integer) result[2]).intValue() / 100);
			}
		}
		return plan;
	}

	@Override
	public BaseEntity get(String entityId) throws NullObjectException,
			InvalidQueryException, ServerException, InvalidValueException {
		String sql = "select {p.*} from lifeplan as p, lifegoal as g where p.planId=? and p.goalId=g.goalId and g.status<>?";
		RdcQuery query = getDbUtil().createNativeSQLQuery(sql);
		Object[] params = { entityId, BaseConst.LIFEGOAL_STATUS_DELETED };
		query.addEntity("p", LifePlan.class);
		query.setParameters(params);
		return (BaseEntity) query.getUniqueResult();
	}

	
}
